package com.lovo.cloud.service.impl;

import com.google.common.hash.BloomFilter;
import com.lovo.cloud.consts.RedisConsts;
import com.lovo.cloud.dao.MovieDAO;
import com.lovo.cloud.model.dos.MovieDO;
import com.lovo.cloud.model.dos.MovieScheduleDO;
import com.lovo.cloud.dao.MovieScheduleDAO;
import com.lovo.cloud.model.dto.MovieScheduleDTO;
import com.lovo.cloud.redis.RedisDAO;
import com.lovo.cloud.service.MovieScheduleService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.lovo.cloud.util.SerializeUtil;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;
import redis.clients.jedis.Jedis;

import javax.annotation.Resource;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author luo
 * @since 2020-12-07
 */
@Service
public class MovieScheduleServiceImpl extends ServiceImpl<MovieScheduleDAO, MovieScheduleDO> implements MovieScheduleService {

    @Resource
    private MovieScheduleDAO movieScheduleDAO;

    @Resource
    private MovieDAO movieDAO;

    @Resource
    private RedissonClient redisson;

    @Resource
    private BloomFilter bloomFilter;

    @Override
    public Map findByMovieId(String movieName) {
        Jedis jedis = RedisDAO.getRedis();
        byte[] bytes = jedis.get(movieName.getBytes());
        if (bytes != null) {
            Map unserizlize = (Map) SerializeUtil.unserizlize(bytes);
            System.out.println(unserizlize);
            return unserizlize;
        }
        MovieDO movieDO = movieDAO.selectByName(movieName);
        List<MovieScheduleDO> movieScheduleDOS = movieScheduleDAO.selectByMovieId(movieDO.getMovieId());
        List<Timestamp> list = new ArrayList<>();
        for (MovieScheduleDO movieScheduleDO : movieScheduleDOS) {
            list.add(movieScheduleDO.getMovieSchedule());
        }
        MovieScheduleDTO movieScheduleDTO = new MovieScheduleDTO();
        movieScheduleDTO.setMovieName(movieName);
        movieScheduleDTO.setSchedules(list);
        Map<String, Object> map = new HashMap<>(16);
        map.put(movieName,movieScheduleDTO);
        jedis.set(movieName.getBytes(),SerializeUtil.serialize(map));
        return map;
    }

    //缓存雪崩
    @Override
    public Map findByMovieName(String movieName) {
        Jedis jedis = RedisDAO.getRedis();
        byte[] bytes = jedis.get(movieName.getBytes());
        if (bytes != null) {
            Map unserizlize = (Map) SerializeUtil.unserizlize(bytes);
            System.out.println(unserizlize);
            return unserizlize;
        }
        MovieDO movieDO = movieDAO.selectByName(movieName);
        List<MovieScheduleDO> movieScheduleDOS = movieScheduleDAO.selectByMovieId(movieDO.getMovieId());
        List<Timestamp> list = new ArrayList<>();
        for (MovieScheduleDO movieScheduleDO : movieScheduleDOS) {
            list.add(movieScheduleDO.getMovieSchedule());
        }
        MovieScheduleDTO movieScheduleDTO = new MovieScheduleDTO();
        movieScheduleDTO.setMovieName(movieName);
        movieScheduleDTO.setSchedules(list);
        Map<String, Object> map = new HashMap<>(16);
        map.put(movieName,movieScheduleDTO);
        int time = (int) ((System.currentTimeMillis()% RedisConsts.REDIS_TIME)+1);

        //设置缓存过期时间为一个时间范围，防止所有缓存同时失效，造成缓存雪崩。
        jedis.setex(movieName.getBytes(),time,SerializeUtil.serialize(map));
        return map;
    }

    //缓存穿透
    @Override
    public Map findByMovieName2(String movieName) {

        //利用布隆算法，查出所查询的电影名字再白名单中是否存在，若不存在，则返回null
        if (!bloomFilter.mightContain(movieName)){
            return null;
        }

        Jedis jedis = RedisDAO.getRedis();
        byte[] bytes = jedis.get(movieName.getBytes());
        if (bytes != null) {
            Map unserizlize = (Map) SerializeUtil.unserizlize(bytes);
            System.out.println(unserizlize);
            return unserizlize;
        }else {
            MovieDO movieDO = movieDAO.selectByName(movieName);
            if (movieDO != null) {
                List<MovieScheduleDO> movieScheduleDOS = movieScheduleDAO.selectByMovieId(movieDO.getMovieId());
                List<Timestamp> list = new ArrayList<>();
                for (MovieScheduleDO movieScheduleDO : movieScheduleDOS) {
                    list.add(movieScheduleDO.getMovieSchedule());
                }
                MovieScheduleDTO movieScheduleDTO = new MovieScheduleDTO();
                movieScheduleDTO.setMovieName(movieName);
                movieScheduleDTO.setSchedules(list);
                Map<String, Object> map = new HashMap<>(16);
                map.put(movieName,movieScheduleDTO);
                int time = (int) ((System.currentTimeMillis()% RedisConsts.REDIS_TIME)+1);

                //设置缓存过期时间为一个时间范围，防止所有缓存同时失效，造成缓存雪崩。
                jedis.setex(movieName.getBytes(),time,SerializeUtil.serialize(map));
                return map;
            }else {
                //如果布隆过滤器有漏网之鱼，则在这里把不存在的数据作为键也放入缓存，值为null。并
                //设置一个较短的过期时间
                jedis.setex(movieName.getBytes(),RedisConsts.PENETRATE_TIME,SerializeUtil.serialize(null));
            }
        }
        return  null;
    }

    //缓存击穿
    @Override
    public Map findByMovieName3(String movieName) throws InterruptedException {


        //利用布隆算法，查出所查询的电影名字再白名单中是否存在，若不存在，则返回null
        if (!bloomFilter.mightContain(movieName)){
            return null;
        }

        Jedis jedis = RedisDAO.getRedis();
        byte[] bytes = jedis.get(movieName.getBytes());
        if (bytes != null) {
            Map unserizlize = (Map) SerializeUtil.unserizlize(bytes);
            System.out.println(unserizlize);
            return unserizlize;
        }else {
            RLock lock = redisson.getLock("movieLock");
            if (lock.tryLock(3,2, TimeUnit.SECONDS)) {
                MovieDO movieDO = movieDAO.selectByName(movieName);
                if (movieDO != null) {
                    List<MovieScheduleDO> movieScheduleDOS = movieScheduleDAO.selectByMovieId(movieDO.getMovieId());
                    List<Timestamp> list = new ArrayList<>();
                    for (MovieScheduleDO movieScheduleDO : movieScheduleDOS) {
                        list.add(movieScheduleDO.getMovieSchedule());
                    }
                    MovieScheduleDTO movieScheduleDTO = new MovieScheduleDTO();
                    movieScheduleDTO.setMovieName(movieName);
                    movieScheduleDTO.setSchedules(list);
                    Map<String, Object> map = new HashMap<>(16);
                    map.put(movieName,movieScheduleDTO);
                    int time = (int) ((System.currentTimeMillis()% RedisConsts.REDIS_TIME)+1);

                    //设置缓存过期时间为一个时间范围，防止所有缓存同时失效，造成缓存雪崩。
                    jedis.setex(movieName.getBytes(),time,SerializeUtil.serialize(map));
                    return map;
                }else {
                    //如果布隆过滤器有漏网之鱼，则在这里把不存在的数据作为键也放入缓存，值为null。并
                    //设置一个较短的过期时间
                    jedis.setex(movieName.getBytes(),RedisConsts.PENETRATE_TIME,SerializeUtil.serialize(null));
                }
            }else {
                Thread.sleep(600);
                return this.findByMovieName3(movieName);
            }
        }
        return null;
    }

    private Map find(String movieName,RLock lock) throws InterruptedException {

        return null;
    }
}
